/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.wider.foundation;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * This base class defines a framework for user to define their object with explicit life-time
 * @author William Choi <avextk@gmail.com>
 */
public class TimeLimited {
    private Expiration         expired;
    private Date            bound;
    private Timer           timer;
    private ExpiringImpl       event;
    private Thread          task;

    /**
     * This interface defines the way for user to mount-in the Expiring event callback
     */
    public interface ExpiringImpl{
        public void expired();
    }

    /**
     * Public inner enum defines the domain of the expiration
     */
    public enum Expiration{
        never, alive, expired;
    }

    /**
     * Default constructor without using self-aging timer
     * @param timeout   timeout value in millis, zero means infinite
     */
    public TimeLimited(long timeout){
        expired = (timeout == 0) ? Expiration.never : Expiration.alive;
        bound   = (timeout == 0) ? null : new Date(new Date().getTime() + timeout);
        timer   = null;
    }

    /**
     * Alternative constructor for user to instanciate this class with using the self-aging timer
     * @param timeout       timeout value in millis, zero means infinite
     * @param callback      the implementation of the Expiring event user-callback
     */
    public TimeLimited(long timeout, ExpiringImpl callback){
        this(timeout);
        event = callback;

        if (null != bound){
            timer = new Timer();
            timer.schedule(new AgingTask(this), bound);
        }
    }

    /**
     * Public method to check if this object is expired. This is non-blocking design
     * @return  True if this object is expired; False otherwise.
     */
    public boolean isExpired(){
        if (!(expired.equals(Expiration.never)) && (expired.equals(Expiration.alive)) && (new Date().compareTo(bound) >= 0))
            expired = Expiration.expired;
        
        return (expired == Expiration.expired);
    }

    /**
     * Publice method to get the time left to the expiration.
     * @return  Time left to the expiration
     */
    public long timeleft(){
        long now = new Date().getTime();
        return (null == bound) ? -1 : bound.getTime() - now;
    }

    /**
     * Public method for user to wait for the Expiration of this instance.
     * @throws InterruptedException
     */
    public synchronized void await() throws InterruptedException {
        if (null == timer){
            timer = new Timer();
            timer.schedule(new AgingTask(this), bound);
        }
        this.wait();
    }
    
    /**
     * Private inner class which defines the self-aging timer task
     */
    private class AgingTask extends TimerTask {
        final TimeLimited t;

        public AgingTask(TimeLimited obj){
            t = obj;
        }

        @Override
        public void run() {
            synchronized(t){
                t.isExpired();
                t.notifyAll();
            }
            t.timer.cancel();
            
            //fire the user-callback
            if (null != t.event)
                t.event.expired();
        }
    }
}
